﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Net.Sockets;
using Pixysoft.Framework.Sockets.Controller;
using Pixysoft.Tools;
using Pixysoft.Framework.Verifications;
using System.IO;

namespace Pixysoft.Framework.Sockets.Core
{
    abstract class SocketIoSession : ISocketIoSession
    {
        Socket clientSocket = null;
        string identifier = null;
        string ip = null;

        int sn = -1;
        bool isDisposed = false;

        SessionProfile profile = null;
        PerformanceAnalyzer performance = null;

        ISessionDecoder decoder = null;
        ISessionEncoder encoder = null;

        Dictionary<string, ISessionDisposable> disposables = new Dictionary<string, ISessionDisposable>();

        byte[] socketBuffer = new byte[1024];//单次异步接受信息的buffer


        //event
        public event SocketExceptionCaught OnSocketExceptionCaught;

        protected SocketIoSession(ISessionEncoder encoder, ISessionDecoder decoder, Socket socket)
        {
            this.clientSocket = socket;
            this.identifier = CreateIdentifier(socket);
            this.ip = socket.RemoteEndPoint.ToString();

            this.clientSocket.SendTimeout = SettingHelper.default_timeout_socket_send;
            this.clientSocket.ReceiveTimeout = SettingHelper.default_timeout_socket_receive;
            this.clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, SettingHelper.default_timeout_socket_send);
            this.clientSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, SettingHelper.default_timeout_socket_receive);


            this.profile = new SessionProfile(identifier);
            this.performance = new PerformanceAnalyzer(Identifier);

            this.decoder = decoder;
            this.encoder = encoder;

            BeginAsyncReceive(socket);
        }

        private void BeginAsyncReceive(Socket socket)
        {
            LoggerHelper.Debug("{0}:socket connection begin recieve data from {1}.", Identifier, Ip);

            socketBuffer = new byte[socketBuffer.Length];

            try
            {
                socket.BeginReceive(socketBuffer, 0, socketBuffer.Length, SocketFlags.None,
                        new AsyncCallback(EndAsyncReceiveCallback), socket);
            }
            catch (Exception ex)
            {
                //任何错误的可能

                ExceptionCaught(ex);

                return;
            }
        }

        private void EndAsyncReceiveCallback(IAsyncResult ar)
        {
            //停止异步

            Socket socket = ar.AsyncState as Socket;

            int recvSize = -1;

            int availableSize = -1;

            try
            {
                recvSize = socket.EndReceive(ar);

                availableSize = socket.Available;

                LoggerHelper.Debug("{0}:socket connection get datasize:{1}, ip:{2}.", Identifier, recvSize, Ip);
            }
            catch (Exception ex)
            {
                //有可能客户端关闭链接导致 也有可能本地关闭listener

                ExceptionCaught(ex);

                return;
            }

            byte[] receiveBytes = socketBuffer;

            //判断是否有有效返回值

            if (recvSize == 0)
            {
                LoggerHelper.Debug("{0}:socket connection is closed by target (receive 0 byte(s)).", Identifier);

                ExceptionCaught(new Pixysoft.UnexpectedException(
                    string.Format("{0}:socket connection is closed by target (receive 0 byte(s)).", Identifier)));

                return;
            }

            //再次接收

            BeginAsyncReceive(socket);


            //获取返回数据

            List<IProtocolMessage> messages = new List<IProtocolMessage>();

            if (!this.decoder.DoDecode(this, receiveBytes, recvSize, availableSize, out messages))
            {
                LoggerHelper.Error("{0}:socket receive available buffer fail. close it.", Identifier);

                ExceptionCaught(new Pixysoft.UnexpectedException(string.Format("{0}:socket receive available buffer fail. close it.", Identifier)));

                return;
            }

            if (messages.Count > 0)
            {
                this.MessageReceived(messages.ToArray());
            }
        }

        protected abstract void MessageReceived(IProtocolMessage[] messages);



        private void SessionClosed()
        {
            LoggerHelper.Info(Identifier + ":socket connection begin close and remove from pool.");

            SessionPoolController.Instance.CloseSession(this);

            GlobalPerformanceAnalyzer.Instance.RemoveSession();// ----------------------------------------------

        }

        private void ExceptionCaught(Exception ex)
        {
            // session is closed unexpectedly

            LoggerHelper.Fatal(this.Identifier + ":socket connection got exception, try close.");

            LoggerHelper.Fatal(ex);

            GlobalPerformanceAnalyzer.Instance.ErrorSession();// ----------------------------------------------

            SessionClosed();

            if (this.OnSocketExceptionCaught != null)
                this.OnSocketExceptionCaught(this, ex);

        }

        private int GetNextSn()
        {
            sn++;

            return sn;
        }



        public void Send(IProtocolMessage message)
        {
            Send(new IProtocolMessage[] { message });
        }

        public void Send(IProtocolMessage[] messages)
        {
            if (this.IsDisposed)
                return;

            byte[] sendBytes = new byte[] { };

            if (!encoder.DoEncode(this, messages, out sendBytes))
            {
                LoggerHelper.Error("{0}:socket send messages fail. close it.", Identifier);

                ExceptionCaught(new Pixysoft.UnexpectedException(string.Format("{0}:socket send messages fail. close it.", Identifier)));

                return;
            }

            if (sendBytes.Length <= 0)
            {
                return;
            }

            Send(sendBytes);
        }

        public void Send(byte[] sendBytes)
        {
            try
            {
                GetSocket().BeginSend(sendBytes, 0, sendBytes.Length, SocketFlags.None, EndAsyncSendCallback,
                       new AsyncSendState(GetSocket(), sendBytes, 0, sendBytes.Length));
            }
            catch (Exception ex)
            {
                //任何错误的可能

                ExceptionCaught(ex);

                return;
            }
        }

        public void SyncSend(byte[] sendBytes)
        {
            GetSocket().Send(sendBytes);
        }

        private void EndAsyncSendCallback(IAsyncResult asyncResult)
        {
            AsyncSendState state = asyncResult.AsyncState as AsyncSendState;

            Socket socket = state.Socket;

            byte[] buffer = state.Buffer;

            int sentBytes = -1;

            try
            {
                sentBytes = socket.EndSend(asyncResult);
            }
            catch (Exception ex)
            {
                ExceptionCaught(ex);

                return;
            }

            int remainBytes = state.Count - sentBytes;

            if (remainBytes <= 0)
            {
                return;
            }

            try
            {
                socket.BeginSend(buffer, buffer.Length - remainBytes, remainBytes, SocketFlags.None, EndAsyncSendCallback,
                    new AsyncSendState(socket, buffer, buffer.Length - remainBytes, remainBytes));
            }
            catch (Exception ex)
            {
                //任何错误的可能

                ExceptionCaught(ex);

                return;
            }
        }




        public void Close()
        {
            if (this.IsDisposed)
                return;

            LoggerHelper.Debug("{0}:socket connection begin close and remove from pool.", Identifier);

            Socket clientSocket = GetSocket();

            this.isDisposed = true;

            if (clientSocket != null)
            {
                try
                {
                    clientSocket.Shutdown(SocketShutdown.Both);
                }
                catch (Exception ex)
                {
                    LoggerHelper.Fatal(ex);
                }

                try
                {
                    clientSocket.Disconnect(false);
                }
                catch (Exception ex)
                {
                    LoggerHelper.Fatal(ex);
                }

                try
                {
                    clientSocket.Close();
                }
                catch (Exception ex)
                {
                    LoggerHelper.Fatal(ex);
                }
            }

            LoggerHelper.Debug("{0}:socket connection end close.", Identifier);
        }

        public virtual void Dispose()
        {
            LoggerHelper.Debug("{0} begin dispose session.", this.Identifier);

            // dispose manual data

            if (this.GetProfile().IsLogin)
            {
                foreach (ISessionDisposable disposable in DisposableList())
                {
                    LoggerHelper.Debug("{0} try dispose session disposable.", Identifier);

                    disposable.Dispose(this.GetProfile().UserCode);
                }
            }

            // dispose default data

            this.GetProfile().Dispose();
            this.GetPerformanceAnalyzer().Dispose();
            this.OnSocketExceptionCaught = null;
        }

        public void Clear()
        {
            LoggerHelper.Debug("{0} begin clear session.", this.Identifier);

            // dispose manual data

            if (this.GetProfile().IsLogin)
            {
                foreach (ISessionDisposable disposable in DisposableList())
                {
                    LoggerHelper.Debug("{0} try dispose session disposable.", this.Identifier);

                    disposable.Dispose(this.GetProfile().UserCode);
                }

                this.GetProfile().Logout();
            }

            this.GetProfile().Dispose();

            ClearDisposableList();
        }

        public bool IsDisposed
        {
            get { return isDisposed; }
        }

        public bool IsTimeout
        {
            get { return GetPerformanceAnalyzer().IsTimeout(); }
        }

        public bool IsLoginningTimeout
        {
            get
            {
                if (this.GetProfile().IsLogin)
                    return false;

                return this.GetPerformanceAnalyzer().CreateDate.AddMilliseconds(SettingHelper.default_timeout_login)
                    < GlobalTimer.Instance.GetGlobalTime();
            }
        }

        public string Identifier
        {
            get { return identifier; }
        }

        public string Ip
        {
            get { return ip; }
            set { ip = value; }
        }



        public void Reset(IProtocolMessage message)
        {
            message.Sn = GetNextSn();
        }

        public IProtocolMessage CreateRequest(int protocol, int action)
        {
            IProtocolMessage message = MessageController.Instance.CreateMessage(protocol, action);
            message.Sn = GetNextSn();
            return message;
        }

        public IProtocolMessage CreateResponse(IProtocolHeader header)
        {
            return MessageController.Instance.CreateMessage(header);
        }

        public IPerformanceAnalyzer GetPerformanceAnalyzer()
        {
            return performance;
        }

        public Socket GetSocket()
        {
            return clientSocket;
        }

        public ISessionProfile GetProfile()
        {
            return this.profile;
        }



        public void RegisterSessionDisposable(string key, ISessionDisposable disposable)
        {
            if (string.IsNullOrEmpty(key))
                throw Exceptions.VerificationFailedException(key, disposable);

            if (disposable == null)
                throw Exceptions.VerificationFailedException(key, disposable);

            LoggerHelper.Debug("{0} REGISTER session disposable:{1}", this.identifier, key);

            this.disposables.Add(key, disposable);
        }

        public void UnregisterSessionDisposable(string key)
        {
            if (this.disposables.ContainsKey(key))
                this.disposables.Remove(key);
        }

        public T GetSessionDisposable<T>(string key)
        {
            return (T)disposables[key];
        }

        private List<ISessionDisposable> DisposableList()
        {
            List<ISessionDisposable> list = new List<ISessionDisposable>();

            foreach (ISessionDisposable value in disposables.Values)
            {
                list.Add(value);
            }

            return list;
        }

        private void ClearDisposableList()
        {
            this.disposables.Clear();
        }


        public static string CreateIdentifier(Socket socket)
        {
            return socket.RemoteEndPoint.ToString().Trim().ToUpper();
        }
    }

    class AsyncSendState
    {
        private byte[] buffer;

        private Socket socket;

        private int offset;

        private int count;


        public AsyncSendState(Socket sock, byte[] buffer, int offset, int count)
        {
            this.socket = sock;

            this.buffer = buffer;

            this.offset = offset;

            this.count = count;
        }

        public byte[] Buffer
        {
            get { return buffer; }
        }

        public Socket Socket
        {
            get { return socket; }
        }

        public int Offset
        {
            get { return offset; }
        }

        public int Count
        {
            get { return count; }
        }
    }
}
